package cc.shacocloud.mirage.core;

import cc.shacocloud.mirage.core.event.EnvironmentChangeEvent;
import cc.shacocloud.mirage.core.support.EnvironmentDiffConfig;
import cc.shacocloud.mirage.env.VertxConfigEnvironment;
import cc.shacocloud.mirage.utils.FutureUtils;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.core.*;
import io.vertx.core.json.JsonObject;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.Objects;

/**
 * 应用环境配置，基于 {@link VertxConfigEnvironment} 拓展，使用 {@link ApplicationEventMulticaster} 通知配置变更事件
 *
 * @author 思追(shaco)
 */
@Slf4j
public class GenericApplicationEnvironment extends VertxConfigEnvironment {
    
    @Setter
    private ApplicationContext applicationContext;
    
    public GenericApplicationEnvironment(@NotNull ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    
    @Override
    protected void doInit() {
        // 临时创建一个 vertx 对象来加载配置使用后释放
        VertxOptions options = new VertxOptions();
        options.setEventLoopPoolSize(1);
        options.setWorkerPoolSize(1);
        options.setInternalBlockingPoolSize(1);
        Vertx vertx = Vertx.vertx(options);
        try {
            ConfigRetrieverOptions retrieverOptions = getRetrieverOptions();
            FutureUtils.await(loadConfig(vertx, retrieverOptions));
        } finally {
            try {
                FutureUtils.await(vertx.close());
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    log.warn("临时的 vertx 对象释放发生例外！", e);
                }
            }
        }
    }
    
    @Override
    public Vertx getVertx() {
        return applicationContext.getVertx();
    }
    
    @Override
    protected Future<Void> configChange(JsonObject oldConfig, JsonObject newConfig) {
        EnvironmentDiffConfig environmentDiffConfig = diffConfig(oldConfig, newConfig);
        
        // 配置变更时发布事件
        return publishEnvironmentChangeEvent(oldConfig, newConfig, environmentDiffConfig);
    }
    
    /**
     * 发布启动成功事件
     */
    protected Future<Void> publishEnvironmentChangeEvent(JsonObject oldConfig,
                                                         JsonObject newConfig,
                                                         EnvironmentDiffConfig environmentDiffConfig) {
        
        // 发布事件
        EnvironmentChangeEvent event = new EnvironmentChangeEvent(this, oldConfig, newConfig, environmentDiffConfig);
        CompositeFuture compositeFuture = applicationContext.publishEvent(event);
        
        Promise<Void> promise = Promise.promise();
        compositeFuture.onComplete(ar -> {
            if (ar.succeeded()) {
                promise.complete();
            } else {
                
                if (log.isErrorEnabled()) {
                    CompositeFuture result = ar.result();
                    for (int i = 0; i < result.size(); i++) {
                        Throwable cause = result.cause(i);
                        if (Objects.isNull(cause)) continue;
                        
                        log.error("配置变更事件监听器处理异常！", cause);
                    }
                }
                promise.fail("发布配置变更事件出现例外！");
            }
        });
        
        return promise.future();
    }
    
    /**
     * 比较两个配置文件之间的差异，返回一个变更的配置对象
     * <p>
     * 规则：<br/>
     * 旧配置中存在且新配置不存在的，为删除的配置 <br/>
     * 新的配置存在且旧的配置不存在的，为更新的配置 <br/>
     * 旧的配置中存在且新的配置也存在的，如果是嵌套对象则进行深层比较，反之新的配置为更新配置
     *
     * @param oldConfig 旧的配置
     * @param newConfig 新的配置对象
     * @return 环境差异配置结果
     */
    protected EnvironmentDiffConfig diffConfig(@NotNull JsonObject oldConfig,
                                               @NotNull JsonObject newConfig) {
        JsonObject updateConfig = new JsonObject();
        JsonObject deleteConfig = new JsonObject();
        
        for (Map.Entry<String, Object> entry : newConfig) {
            String key = entry.getKey();
            Object value = entry.getValue();
            Object oldValue = oldConfig.getValue(key);
            
            // 都为空，跳过
            if (Objects.isNull(value) && Objects.isNull(oldValue)) {
                continue;
            }
            // 旧的配置为空
            else if (Objects.isNull(oldValue)) {
                updateConfig.put(key, value);
            }
            // 新的配置为空
            else if (Objects.isNull(value)) {
                deleteConfig.put(key, oldValue);
            }
            // 新旧配置不相同
            else if (!value.equals(oldValue)) {
                
                // 如果都是嵌套对象则深入比对
                if (value instanceof JsonObject && oldValue instanceof JsonObject) {
                    EnvironmentDiffConfig diffConfig = diffConfig((JsonObject) oldValue, (JsonObject) value);
                    diffConfig.getUpdateConfig().forEach(e -> updateConfig.put(e.getKey(), e.getValue()));
                    diffConfig.getDeleteConfig().forEach(e -> deleteConfig.put(e.getKey(), e.getValue()));
                }
                // 否则写入更新配置
                else {
                    updateConfig.put(key, value);
                }
            }
        }
        
        return new EnvironmentDiffConfig(updateConfig, deleteConfig);
    }
}
